package monitoring

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/google/uuid"
	"github.com/sirupsen/logrus"
)

// MetricType represents the type of metric
type MetricType string

const (
	MetricTypeCounter   MetricType = "counter"
	MetricTypeGauge     MetricType = "gauge"
	MetricTypeHistogram MetricType = "histogram"
)

// Metric represents a single metric
type Metric struct {
	ID          string
	Name        string
	Type        MetricType
	Value       float64
	Labels      map[string]string
	Timestamp   time.Time
	Description string
}

// HealthStatus represents the health status
type HealthStatus string

const (
	HealthStatusHealthy   HealthStatus = "healthy"
	HealthStatusDegraded  HealthStatus = "degraded"
	HealthStatusUnhealthy HealthStatus = "unhealthy"
)

// HealthCheck represents a health check result
type HealthCheck struct {
	ID        string
	Name      string
	Status    HealthStatus
	Message   string
	Details   map[string]interface{}
	Timestamp time.Time
}

// Alert represents a monitoring alert
type Alert struct {
	ID        string
	Name      string
	Level     string
	Message   string
	Labels    map[string]string
	Timestamp time.Time
	Status    string
}

// Monitor handles system monitoring and metrics collection
type Monitor struct {
	metrics       map[string]*Metric
	healthChecks  map[string]*HealthCheck
	alerts        map[string]*Alert
	thresholds    map[string]float64
	alertChannels []chan *Alert
	logger        *logrus.Entry
	mutex         sync.RWMutex
}

// NewMonitor creates a new monitoring system
func NewMonitor() *Monitor {
	return &Monitor{
		metrics:       make(map[string]*Metric),
		healthChecks:  make(map[string]*HealthCheck),
		alerts:        make(map[string]*Alert),
		thresholds:    make(map[string]float64),
		alertChannels: make([]chan *Alert, 0),
		logger:        logrus.WithField("component", "monitor"),
	}
}

// RecordMetric records a new metric value
func (m *Monitor) RecordMetric(metric *Metric) error {
	m.mutex.Lock()
	defer m.mutex.Unlock()

	if metric.ID == "" {
		metric.ID = uuid.NewString()
	}
	metric.Timestamp = time.Now()

	m.metrics[metric.ID] = metric

	// Check thresholds and generate alerts if needed
	if threshold, exists := m.thresholds[metric.Name]; exists {
		if metric.Value > threshold {
			alert := &Alert{
				ID:        uuid.NewString(),
				Name:      fmt.Sprintf("%s_threshold_exceeded", metric.Name),
				Level:     "warning",
				Message:   fmt.Sprintf("Metric %s exceeded threshold: %f > %f", metric.Name, metric.Value, threshold),
				Labels:    metric.Labels,
				Timestamp: time.Now(),
				Status:    "active",
			}
			m.alerts[alert.ID] = alert
			m.broadcastAlert(alert)
		}
	}

	return nil
}

// GetMetric retrieves a metric by ID
func (m *Monitor) GetMetric(id string) (*Metric, error) {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	metric, exists := m.metrics[id]
	if !exists {
		return nil, fmt.Errorf("metric with ID %s not found", id)
	}

	return metric, nil
}

// RecordHealthCheck records a health check result
func (m *Monitor) RecordHealthCheck(check *HealthCheck) error {
	m.mutex.Lock()
	defer m.mutex.Unlock()

	if check.ID == "" {
		check.ID = uuid.NewString()
	}
	check.Timestamp = time.Now()

	m.healthChecks[check.ID] = check

	// Generate alert for unhealthy status
	if check.Status == HealthStatusUnhealthy {
		alert := &Alert{
			ID:        uuid.NewString(),
			Name:      fmt.Sprintf("%s_health_check_failed", check.Name),
			Level:     "critical",
			Message:   fmt.Sprintf("Health check %s failed: %s", check.Name, check.Message),
			Timestamp: time.Now(),
			Status:    "active",
		}
		m.alerts[alert.ID] = alert
		m.broadcastAlert(alert)
	}

	return nil
}

// GetHealthCheck retrieves a health check by ID
func (m *Monitor) GetHealthCheck(id string) (*HealthCheck, error) {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	check, exists := m.healthChecks[id]
	if !exists {
		return nil, fmt.Errorf("health check with ID %s not found", id)
	}

	return check, nil
}

// SetThreshold sets a threshold for a metric
func (m *Monitor) SetThreshold(metricName string, threshold float64) {
	m.mutex.Lock()
	defer m.mutex.Unlock()
	m.thresholds[metricName] = threshold
}

// SubscribeAlerts subscribes to alert notifications
func (m *Monitor) SubscribeAlerts() <-chan *Alert {
	m.mutex.Lock()
	defer m.mutex.Unlock()

	ch := make(chan *Alert, 100)
	m.alertChannels = append(m.alertChannels, ch)
	return ch
}

// UnsubscribeAlerts unsubscribes from alert notifications
func (m *Monitor) UnsubscribeAlerts(ch <-chan *Alert) {
	m.mutex.Lock()
	defer m.mutex.Unlock()

	for i, channel := range m.alertChannels {
		if channel == ch {
			close(channel)
			m.alertChannels = append(m.alertChannels[:i], m.alertChannels[i+1:]...)
			return
		}
	}
}

// broadcastAlert broadcasts an alert to all subscribers
func (m *Monitor) broadcastAlert(alert *Alert) {
	for _, ch := range m.alertChannels {
		select {
		case ch <- alert:
		default:
			m.logger.Warn("Alert channel full, dropping alert")
		}
	}
}

// GetSystemHealth returns overall system health
func (m *Monitor) GetSystemHealth() HealthStatus {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	unhealthyCount := 0
	degradedCount := 0

	for _, check := range m.healthChecks {
		switch check.Status {
		case HealthStatusUnhealthy:
			unhealthyCount++
		case HealthStatusDegraded:
			degradedCount++
		}
	}

	if unhealthyCount > 0 {
		return HealthStatusUnhealthy
	}
	if degradedCount > 0 {
		return HealthStatusDegraded
	}
	return HealthStatusHealthy
}

// GetActiveAlerts returns all active alerts
func (m *Monitor) GetActiveAlerts() []*Alert {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	activeAlerts := make([]*Alert, 0)
	for _, alert := range m.alerts {
		if alert.Status == "active" {
			activeAlerts = append(activeAlerts, alert)
		}
	}
	return activeAlerts
}

// ResolveAlert marks an alert as resolved
func (m *Monitor) ResolveAlert(alertID string) error {
	m.mutex.Lock()
	defer m.mutex.Unlock()

	alert, exists := m.alerts[alertID]
	if !exists {
		return fmt.Errorf("alert with ID %s not found", alertID)
	}

	alert.Status = "resolved"
	return nil
}

// GetMetrics returns all metrics
func (m *Monitor) GetMetrics() []*Metric {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	metrics := make([]*Metric, 0, len(m.metrics))
	for _, metric := range m.metrics {
		metrics = append(metrics, metric)
	}
	return metrics
}

// GetHealthChecks returns all health checks
func (m *Monitor) GetHealthChecks() []*HealthCheck {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	checks := make([]*HealthCheck, 0, len(m.healthChecks))
	for _, check := range m.healthChecks {
		checks = append(checks, check)
	}
	return checks
}

// Start starts the monitoring system
func (m *Monitor) Start(ctx context.Context) {
	go m.run(ctx)
}

// run is the main monitoring loop
func (m *Monitor) run(ctx context.Context) {
	ticker := time.NewTicker(1 * time.Minute)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			m.cleanup()
		}
	}
}

// cleanup removes old metrics and resolved alerts
func (m *Monitor) cleanup() {
	m.mutex.Lock()
	defer m.mutex.Unlock()

	now := time.Now()
	threshold := now.Add(-24 * time.Hour)

	// Clean up old metrics
	for id, metric := range m.metrics {
		if metric.Timestamp.Before(threshold) {
			delete(m.metrics, id)
		}
	}

	// Clean up resolved alerts
	for id, alert := range m.alerts {
		if alert.Status == "resolved" && alert.Timestamp.Before(threshold) {
			delete(m.alerts, id)
		}
	}
}

// GetMetricsByType returns metrics of a specific type
func (m *Monitor) GetMetricsByType(metricType MetricType) []*Metric {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	metrics := make([]*Metric, 0)
	for _, metric := range m.metrics {
		if metric.Type == metricType {
			metrics = append(metrics, metric)
		}
	}
	return metrics
}

// GetMetricsByLabel returns metrics with specific labels
func (m *Monitor) GetMetricsByLabel(labels map[string]string) []*Metric {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	metrics := make([]*Metric, 0)
	for _, metric := range m.metrics {
		matches := true
		for k, v := range labels {
			if metric.Labels[k] != v {
				matches = false
				break
			}
		}
		if matches {
			metrics = append(metrics, metric)
		}
	}
	return metrics
}

// GetAlertsByLevel returns alerts of a specific level
func (m *Monitor) GetAlertsByLevel(level string) []*Alert {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	alerts := make([]*Alert, 0)
	for _, alert := range m.alerts {
		if alert.Level == level {
			alerts = append(alerts, alert)
		}
	}
	return alerts
}

// GetHealthChecksByStatus returns health checks with a specific status
func (m *Monitor) GetHealthChecksByStatus(status HealthStatus) []*HealthCheck {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	checks := make([]*HealthCheck, 0)
	for _, check := range m.healthChecks {
		if check.Status == status {
			checks = append(checks, check)
		}
	}
	return checks
}
